/*
 * @Author: fisher
 * @Date: 2024-02-26 11:09:16
 * @LastEditors: luthor luthor-li@qq.com
 * @LastEditTime: 2024-03-10 02:41:07
 * @FilePath: \allback\services\ueinstanceService.js
 * @Description:
 */
// services/ueInstanceService.js
const { execSync } = require('child_process')
const fs = require('fs-extra')
const path = require('path')
const UeInstance = require('../models/ueinstanceModel') // 导入你的Mongoose模型
// const UefileModel = require('../models/uefileModel')
const Cluster = require('../models/clusterModel')
const { post } = require('../utils/request')
const { copysignalling, generateStartconfig } = require('../utils/ueconfig')
const {
  addUserConnection,
  removeUserConnection,
  callSignalCloseClient
} = require('../utils/ueClient')
const wsService = require('./wsService')

/**
 * @description: 创建实例
 * @param {*} data
 * @return {*}
 */
const createInstance = async data => {
  const duplicate = await UeInstance.findOne({
    $or: [{ name: data.name }, { port: data.port }]
  })
  if (duplicate) {
    throw new HttpError('已存在相同名称或端口的实例！', 409)
  }
  const instanceData = {
    instanceIp: process.env.HOST,
    ...data
  }
  const instance = new UeInstance(instanceData)
  await instance.save()
  // 创建实例后，对应复制一份信令服务器
  copysignalling(instance)
  return instance
}

/**
 * @description: 根据id获得实例数据
 * @param {*} id
 * @return {*}
 */
const getInstanceById = async id => {
  const instance = await UeInstance.findById(id)
  return instance
}

/**
 * @description: 分页查询实例列表
 * @param {*} pageSize
 * @param {*} pageNo
 * @param {*} search
 * @return {*}
 */
const getInstanceList = async (pageSize, pageNo, search) => {
  try {
    const query = {}
    // 设置默认的分页参数
    pageNo = parseInt(pageNo) || 1
    pageSize = parseInt(pageSize) || 10
    const skip = (pageNo - 1) * pageSize
    if (search) {
      query.name = new RegExp(search, 'i') // 用于模糊搜索的正则表达式
    }
    // 计算总记录数
    const total = await UeInstance.countDocuments(query)
    // 获得分页数据
    const records = await UeInstance.find(query)
      .sort({ createdAt: -1 })
      .skip(skip)
      .limit(pageSize)
      .exec()
    return { records, total }
  } catch (err) {
    throw new HttpError('查询错误！', 400)
  }
}

const updateInstanceById = async (id, updates) => {
  const updatedInstance = await UeInstance.findByIdAndUpdate(id, updates, {
    new: true
  })
  return updatedInstance
}

/**
 * @description: 删除实例
 * @param {*} ids
 * @return {*}
 */
const deleteInstance = async ids => {
  try {
    // 删除实例
    // 根据id查询数据
    const deleteData = await UeInstance.find({ _id: { $in: ids } }).exec()
    // 定义发送给集群节点的请求列表
    const requests = []
    const requestMap = new Map()
    // 删除pm2进程
    let deleteStr = ''
    for (let i = 0, len = deleteData.length; i < len; i++) {
      if (deleteData[i].runTimestamp) {
        // 找到集群节点
        const clusterNode = await Cluster.findById(deleteData[i].clusterId)
        // 设置请求地址
        const url = `${clusterNode.protocol}://${clusterNode.host}:${clusterNode.port}/ueinstance/deleteUeinstance`
        // 当map中已经存在了clusterid，则查询数据，推入数据
        if (requestMap.has(clusterNode._id.toString())) {
          requestMap.get(clusterNode._id.toString()).ids.push(deleteData[i]._id)
        } else {
          // 当map不存在clusterid节点，则新建数据传入
          requestMap.set(clusterNode._id.toString(), {
            url,
            ids: [deleteData[i]._id]
          })
        }
        // 根据实例信息，分别获取信令服务名称和ue服务名称
        deleteStr += `server_${deleteData[i]._id} `
      }
    }
    // 关闭集群节点ue服务
    // 根据requestMap数据整合发送请求给集群节点
    for (let value of requestMap.values()) {
      requests.push(post(value.url, { ids: value.ids }))
    }
    // 统一发送请求
    await Promise.all(requests)
    // 停止主控信令服务
    // 当删除数据字符串存在时，才进行pm2删除
    if (deleteStr) {
      const deleteCmd = `pm2 delete ${deleteStr}`
      execSync(deleteCmd)
    }
    // 删除实例信令服务克隆体
    for (let i = 0, len = deleteData.length; i < len; i++) {
      const deletePath = path.resolve(
        __dirname,
        `../signalling/${deleteData[i].name}`
      )
      await fs.remove(deletePath)
    }
    // 删除数据库记录
    await UeInstance.deleteMany({ _id: { $in: ids } })
  } catch (err) {
    throw new HttpError(err.message, 400)
  }
}

const startInstances = async ids => {
  try {
    // 获取启动实例数据
    const instances = await UeInstance.find({ _id: { $in: ids } })
    console.log('instances==', instances)
    // 生成实例启动配置文件
    const configs = []
    // 根据实例绑定的uefile id去获取uefile数据
    for (let i = 0, len = instances.length; i < len; i++) {
      configs.push(instances[i])
      // const id = instances[i].projectId
      // const uefiles = await UefileModel.findById(id)
      // console.log('uefiles==', uefiles)
      // config.push({
      //   id: instances[i]._id,
      //   instanceName: instances[i].name,
      //   instanceIp: instances[i].instanceIp,
      //   instancePort: instances[i].port,
      //   ueName: uefiles.name,
      //   uePath: uefiles.path
      // })
    }
    const configPath = await generateStartconfig(configs)
    if (configPath) {
      // 根据启动配置文件启动实例列表
      const startCmd = `pm2 start ${configPath}`
      execSync(startCmd)
      // 启动后更新实例数据
      const filter = { _id: { $in: ids } }
      const update = { $set: { runStatus: true, runTimestamp: Date.now() } }
      await UeInstance.updateMany(filter, update)
    } else {
      throw new HttpError('启动失败！', 400)
    }
  } catch (err) {
    throw new HttpError(err.message, 400)
  }
}
/**
 * @description: 停止实例，可多个
 * @param {*} ids
 * @return {*}
 */
const stopInstances = async ids => {
  try {
    // 获取所有需要停止的实例信息
    const instances = await UeInstance.find({ _id: { $in: ids } })
    // 循环遍历停止实例
    // 定义发送给集群节点的请求列表
    const requests = []
    const requestMap = new Map()
    for (let i = 0, len = instances.length; i < len; i++) {
      // 整合集群节点信息，准备发送请求到集群节点
      // 找到集群节点
      const clusterNode = await Cluster.findById(instances[i].clusterId)
      // 设置请求地址
      const url = `${clusterNode.protocol}://${clusterNode.host}:${clusterNode.port}/ueinstance/stopUeinstance`
      // 当map中已经存在了clusterid，则查询数据，推入数据
      if (requestMap.has(clusterNode._id.toString())) {
        requestMap.get(clusterNode._id.toString()).ids.push(instances[i]._id)
      } else {
        // 当map不存在clusterid节点，则新建数据传入
        requestMap.set(clusterNode._id.toString(), {
          url,
          ids: [instances[i]._id]
        })
      }
      // 当存在运行时间戳时表示此实例已经运行过，在pm2中已经注册了进程。
      if (instances[i].runTimestamp && instances[i].runStatus) {
        // 根据实例id,停止pm2中对应信令进程
        const stopCmd = `pm2 stop server_${instances[i]._id}`
        execSync(stopCmd)
        // 计算运行时长
        const runtime = Date.now() - instances[i].runTimestamp
        // 停止实例后，更新数据
        const filter = { _id: instances[i]._id }
        const update = {
          $set: {
            runStatus: false,
            totalRuntimes: instances[i].totalRuntimes + runtime,
            connectStatus: false,
            connections: []
          }
        }
        // 更新数据库中所有 runStatus 为 true 的文档，将 runStatus 设置为 false
        await UeInstance.updateOne(filter, update)
      }
    }
    // 根据requestMap数据整合发送请求给集群节点
    for (let value of requestMap.values()) {
      console.log('value ==', value)
      requests.push(post(value.url, { ids: value.ids }))
    }
    // 统一发送请求
    await Promise.all(requests)
  } catch (err) {
    throw new HttpError(err.message, 400)
  }
}

/**
 * @description: 获取所有进程运行状态
 * @return {*}
 */
const getInstanceStatus = async () => {
  try {
    const listCmd = `pm2 jlist`
    const status = execSync(listCmd)
    return status
  } catch (err) {
    throw new HttpError(err.message, 400)
  }
}

const msgBySignal = async (
  instanceId,
  userId,
  playerId,
  clientIp,
  signalHost,
  signalPort,
  useHttps,
  connectStatus
) => {
  if (connectStatus) {
    const data = {
      userId,
      playerId,
      clientIp,
      signalHost,
      signalPort,
      useHttps
    }
    await addUserConnection(instanceId, data)
  } else {
    await removeUserConnection(instanceId, userId)
  }

  // 执行完更新操作后，发送websockt信息
  wsService.broadcastMessage({
    type: 'watch',
    data: {
      target: 'ueinstance',
      key: 'connectChange',
      message: '更新实例连接状态'
    }
  })
}
const closeClient = async (instanceId, connectionId) => {
  try {
    const instance = await UeInstance.findById(instanceId)
    if (instance) {
      const connectData = instance.connections.find(
        item => item._id == connectionId
      )
      if (connectData) {
        //根据获取到的connect数据去调用关闭客户端的方法
        const res = await callSignalCloseClient(instanceId, connectData)
        return res
      }
    }
  } catch (err) {
    throw new HttpError(err.message, 400)
  }
}
module.exports = {
  createInstance,
  getInstanceById,
  getInstanceList,
  updateInstanceById,
  deleteInstance,
  startInstances,
  stopInstances,
  // stopInstanceByid,
  getInstanceStatus,
  msgBySignal,
  closeClient
}
